#![cfg_attr(not(feature = "std"), no_std)]

use liquid::storage;
use liquid_lang as liquid;
use liquid_lang::State;

#[liquid::contract]
mod Ballot {
    use super::*;

    #[derive(Debug, State)]
    struct Voter {
        weight:u32,
        voted:bool,
        delegate:address,
        vote:i32,
    }

    #[derive(Debug, State)]
    struct Proposal {
        content:string,
        voteCount:u32,
    }

    #[liquid(storage)]
    struct BallotVote {
        pub chairperson: storage::Value<address>,
        pub voters: storage::IterableMapping<address,Voter>,
        pub proposals:storage::Vec<Proposal>,
    }

     #[liquid(methods)]
    impl BallotVote {
        pub fn new(&mut self) 
        {
            self.chairperson.initialize(self.env().get_caller());
            self.proposals.initialize();
            self.voters[chairperson].weight = 1;
        }

        pub fn authentic(&mut self, addr:address, voter:Voter)
        {
            self.voters.insert(&addr, voter);
        }

        pub fn new_voter(&mut self, addr:address)
        {
            let voter = Voter
            {
                weight:1,
                voted:false,
                vote:-1,
                delegate: addr
            };
            self.authentic(addr, voter);
        }


        pub fn new_proposal(&mut self, content:String)
        {
            let proposal = Proposal
            {
                content: content,
                voteCount: 0
            };
            self.proposals.push(proposal);
        }

        pub fn vote(&mut self, proposal:u32, addr:address)
        {
            let voter = self.voters.get_mut(&addr).unwrap();
            require(voter.voted == false, "Voter has voted or delegate to other voter");
            self.proposals[proposal].voteCount += voter.weight;
            let new_voter = Voter
            {
                weight: voter.weight,
                voted: true,
                vote: proposal as i32,
                delegate: voter.delegate
            };
            self.voters.insert(&addr,new_voter);
        }

        pub fn delegate(&mut self, _delegater:address,_delegatee:address)
        {
            let delegater = self.voters.get_mut(&_delegater).unwrap();
            require(delegater.voted == false, "Delegater has voted");
            let new_delegater =  Voter
            {
                weight: 0,
                voted: true,
                vote: -1,
                delegate: _delegatee,
            };

            let temp = delegater.weight;
            let delegatee = self.voters.get_mut(&_delegatee).unwrap();
            require(delegatee.voted == false, "Delegatee has voted");
            let new_delegatee = Voter
            {
                weight: delegatee.weight + temp,
                voted: false,
                vote: -1,
                delegate: delegatee.delegate
            };
            delegatee.weight += 1;
            self.voters.insert(&_delegater,new_delegater);
            self.voters.insert(&_delegatee,new_delegatee);
        }

        pub fn get_winner_content(&self) -> String
        {
            let mut winner_voteCount = 0;
            let mut winner_vote_content = String::from("");
            for i in 0..self.proposals.len()
            {
                if self.proposals[i].voteCount > winner_voteCount
                {
                    winner_voteCount = self.proposals[i].voteCount;
                    winner_vote_content = self.proposals[i].content.clone();
                }
            }
            winner_vote_content
        }
    }

     #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn delegate_test()
        {
            let mut ballot_test = Ballot::new();
            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);
            let voter1 = ballot_test.voters.get_mut(&addr1).unwrap();
            assert_eq!(voter1.weight, 1);
            assert_eq!(voter1.voted, false);
            assert_eq!(voter1.vote, -1);
            assert_eq!(voter1.delegate, addr1);
            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);
            let voter2 = ballot_test.voters.get_mut(&addr2).unwrap();
            assert_eq!(voter2.weight, 1);
            assert_eq!(voter2.voted, false);
            assert_eq!(voter2.vote, -1);
            assert_eq!(voter2.delegate, addr2);
            let addr3 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3]};
            ballot_test.new_voter(addr3);
            let voter3 = ballot_test.voters.get_mut(&addr3).unwrap();
            assert_eq!(voter3.weight, 1);
            assert_eq!(voter3.voted, false);
            assert_eq!(voter3.vote, -1);
            assert_eq!(voter3.delegate, addr3);
            ballot_test.delegate(addr1,addr2);
            let voter1 = ballot_test.voters.get_mut(&addr1).unwrap();
            assert_eq!(voter1.weight, 0);
            assert_eq!(voter1.voted, true);
            assert_eq!(voter1.vote, -1);
            assert_eq!(voter1.delegate, addr2);
            let voter2 = ballot_test.voters.get_mut(&addr2).unwrap();
            assert_eq!(voter2.weight, 2);
            assert_eq!(voter2.voted, false);
            assert_eq!(voter2.vote, -1);
            assert_eq!(voter2.delegate, addr2);
            let voter3 = ballot_test.voters.get_mut(&addr3).unwrap();
            assert_eq!(voter3.weight, 1);
            assert_eq!(voter3.voted, false);
            assert_eq!(voter3.vote, -1);
            assert_eq!(voter3.delegate, addr3);
            ballot_test.delegate(addr2,addr3);
            let voter1 = ballot_test.voters.get_mut(&addr1).unwrap();
            assert_eq!(voter1.weight, 0);
            assert_eq!(voter1.voted, true);
            assert_eq!(voter1.vote, -1);
            assert_eq!(voter1.delegate, addr2);
            let voter2 = ballot_test.voters.get_mut(&addr2).unwrap();
            assert_eq!(voter2.weight, 0);
            assert_eq!(voter2.voted, true);
            assert_eq!(voter2.vote, -1);
            assert_eq!(voter2.delegate, addr3);
            let voter3 = ballot_test.voters.get_mut(&addr3).unwrap();
            assert_eq!(voter3.weight, 3);
            assert_eq!(voter3.voted, false);
            assert_eq!(voter3.vote, -1);
            assert_eq!(voter3.delegate, addr3);
        }

        #[test]
        fn authentic_test()
        {
            let addr = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            let voter_1 = Voter
            {
                weight:1,
                voted:false,
                vote:-1,
                delegate: addr
            };
            let mut ballot_test = Ballot::new();
            ballot_test.authentic(addr,voter_1);
            assert_eq!(ballot_test.voters.get(&addr).unwrap().weight,1);
            assert_eq!(ballot_test.voters.get(&addr).unwrap().voted,false);
            assert_eq!(ballot_test.voters.get(&addr).unwrap().vote,-1);
            assert_eq!(ballot_test.voters.get(&addr).unwrap().delegate,addr);
        }

        #[test]
        fn get_winner_content_test()
        {
            let mut ballot_test = Ballot::new();
            let content1 = String::from("content1");
            ballot_test.new_proposal(content1);
            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);
            let content2 = String::from("content2");
            ballot_test.new_proposal(content2);
            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);
            let content3 = String::from("content3");
            ballot_test.new_proposal(content3);
            let addr3 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3]};
            ballot_test.new_voter(addr3);
            assert_eq!(ballot_test.get_winner_content(), "");
            ballot_test.vote(0,addr1);
            assert_eq!(ballot_test.get_winner_content(), "content1");
            ballot_test.vote(1,addr2);
            assert_eq!(ballot_test.get_winner_content(), "content1");
            ballot_test.vote(1,addr3);
            assert_eq!(ballot_test.get_winner_content(), "content2");

        }

        #[test]
        fn vote_test()
        {
            let mut ballot_test = Ballot::new();
            let content1 = String::from("content1");
            ballot_test.new_proposal(content1);
            let content2 = String::from("content2");
            ballot_test.new_proposal(content2);
            let content3 = String::from("content3");
            ballot_test.new_proposal(content3);
            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);
            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);
            assert_eq!(ballot_test.proposals[0].voteCount, 0);
            assert_eq!(ballot_test.proposals[1].voteCount, 0);
            assert_eq!(ballot_test.proposals[2].voteCount, 0);
            ballot_test.vote(0,addr1);
            assert_eq!(ballot_test.proposals[0].voteCount, 1);
            assert_eq!(ballot_test.proposals[1].voteCount, 0);
            assert_eq!(ballot_test.proposals[2].voteCount, 0);
            assert_eq!(ballot_test.voters.get(&addr1).unwrap().voted,true);
            assert_eq!(ballot_test.voters.get(&addr1).unwrap().vote,0);
            ballot_test.vote(1,addr2);
            assert_eq!(ballot_test.proposals[0].voteCount, 1);
            assert_eq!(ballot_test.proposals[1].voteCount, 1);
            assert_eq!(ballot_test.proposals[2].voteCount, 0);
            assert_eq!(ballot_test.voters.get(&addr2).unwrap().voted,true);
            assert_eq!(ballot_test.voters.get(&addr2).unwrap().vote,1);
        }
    }
}